package com.sunday.common.netty.e1_Hello_World;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;


/**
 * https://netty.io/wiki/user-guide-for-4.x.html
 * 处理服务器端通道。
 * Handles a server-side channel.
 */

/**
 * DiscardServerHandler扩展 ChannelInboundHandlerAdapter，它是 ChannelInboundHandler 的实现。
 * ChannelInboundHandler 提供了可以重写的各种事件处理程序方法。目前，只需扩展 ChannelInboundHandlerAdapter 即可，而不是自己实现处理程序接口。
 */
@Slf4j
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

    /**
     * 我们在这里重写事件处理程序方法。每当从客户端接收到新数据时，都会使用收到的消息调用此方法。
     * 在此示例中，收到的消息类型为 ByteBuf。channelRead()
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        /**
         * 若要实现该协议，处理程序必须忽略收到的消息。ByteBuf 是一个引用计数的对象，必须通过该方法显式释放。
         * 请记住，处理程序负责释放传递给处理程序的任何引用计数对象。通常，handler 方法的实现方式如下：DISCARD release() channelRead()
         */
        // 静默丢弃接收到的数据。
//        ((ByteBuf) msg).release(); // (3)
//        log.info("channelRead: {}", msg);
        if (msg instanceof ByteBuf in) {
            try {
                /**
                 * 这个低效的循环实际上可以简化为：System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))
                 * in.isReadable() 返回 true，只要有更多的可供读取的字节。
                 */
//                while (in.isReadable()) { // (1)
//                    System.out.print((char) in.readByte());
//                    System.out.flush();
//                }
                String message = in.toString(CharsetUtil.US_ASCII);
                log.info("{}", message);

                /**
                 * ChannelHandlerContext 对象提供各种操作，使您能够触发各种 I/O 事件和操作。
                 * 在这里，我们调用以逐字逐句地编写收到的消息。请注意，与示例中不同，我们没有发布收到的消息。
                 * 这是因为 Netty 在写入线路时会为您释放它。write(Object)DISCARD
                 */
//                ctx.write(msg); // (1)
                /**
                 * ctx.write(Object)不会使消息写出到电线上。
                 * 它在内部缓冲，然后通过 .或者，您可以要求简洁。ctx.flush() ctx.writeAndFlush(msg)
                 */
//                ctx.flush(); // (2)

                /**
                 * writeAndFlush 是一个合并方法，它等效于调用 write(msg) 和 flush()。
                 */

                /**
                 * ByteBuf 是一个引用计数的对象，必须通过该方法显式释放。
                 * 在 Netty 中，ByteBuf 是用来操作字节数据的缓冲区。以下是 ByteBuf 常见方法的解释：
                 * {@link ByteBuf#writeByte(int)} 写入一个字节。
                 * {@link ByteBuf#readByte()} 读取一个字节。
                 * {@link ByteBuf#writeBytes(byte[])} 写入一个字节数组。
                 * {@link ByteBuf#readBytes(byte[])} 读取一个字节数组。
                 * {@link ByteBuf#writeBytes(ByteBuf)} 写入一个 ByteBuf。
                 * {@link ByteBuf#readBytes(ByteBuf)} 读取一个 ByteBuf。
                 * {@link ByteBuf#writeBytes(ByteBuf, int, int)} 写入一个 ByteBuf 的一部分。
                 * {@link ByteBuf#readBytes(ByteBuf, int, int)} 读取一个 ByteBuf 的一部分。
                 * {@link ByteBuf#alloc()} 分配一个新的 ByteBuf。
                 */

                ByteBuf byteBuf = in.alloc().buffer().writeBytes(("response" + message).getBytes());

//                ByteBuf byteBuf = Unpooled.buffer().writeBytes(("response" + message).getBytes());
                ctx.writeAndFlush(byteBuf);

            } finally {
                /**
                 * 或者，你可以在这里做。in.release()
                 */
                // ReferenceCountUtil.release 是一个静态方法，可以方便地处理引用计数对象。
                ReferenceCountUtil.release(msg);
            }
        }

        /**
         * IllegalReferenceCountException: refCnt: 0, decrement: 1
         * 表示在尝试释放 Netty 消息的引用计数时出现了问题。具体来说，refCnt: 0 表示消息的引用计数为 0，
         * 而 decrement: 1 表示要减少的数量为 1。这个错误表明您尝试在一个引用计数已经为 0 的对象上进行释放操作，这是不允许的。
         * 通常情况下，这种情况会导致异常发生。
         * 这种错误通常由以下情况引起：
         * 多次释放：可能在代码中多次调用了消息的释放方法，导致引用计数变为负数。
         * 并发访问：在多线程环境下，可能出现并发访问同一消息对象并尝试同时释放它的情况。
         * 消息所有权问题：消息的所有权可能在预期之外被转移，导致消息在不应该释放的时候被释放。
         * 解决这个问题的方法是仔细检查代码，确保在正确的时间和地点对消息进行引用计数的增加和减少，并确保在多线程环境下正确地处理消息的共享和所有权。
         */

    }

    /**
     * 当 Netty 由于 I/O 错误引发异常或处理程序实现由于处理事件时引发的异常而引发异常时，使用 Throwable 调用事件处理程序方法。
     * 在大多数情况下，应记录捕获的异常，并在此处关闭其关联的通道，尽管此方法的实现可能会有所不同，具体取决于要执行的操作来处理异常情况。
     * 例如，您可能希望在关闭连接之前发送带有错误代码的响应消息。exceptionCaught()
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // 当引发异常时关闭连接。
        cause.printStackTrace();
        ctx.close();
    }

}
